import exceptions.BadInstructionException;
import utils.DataUtils;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @ClassName: InstructionsDecoder
 * @Description: TODO
 * @author: DJ008854
 * @date: 2020年10月27日 17:23
 */
public class InstructionsDecoder {

    private static Map<String,String> codeMap = new HashMap<>();
    private static Map<String,String> registersAddressMap = new HashMap<>();
    private static Map<String,String> functMap = new HashMap<>();
    static {

        codeMap.put("add","000000");
        codeMap.put("sub","000000");
        codeMap.put("addi","001000");
        codeMap.put("sw","101011");
        codeMap.put("lw","100011");
        codeMap.put("beq","000100");
        codeMap.put("jump","000010");


        registersAddressMap.put("r0","00000");
        registersAddressMap.put("r1","00001");
        registersAddressMap.put("r2","00010");
        registersAddressMap.put("r3","00011");
        registersAddressMap.put("r4","00100");
        registersAddressMap.put("r5","00101");
        registersAddressMap.put("r6","00110");
        registersAddressMap.put("rzero","00111");

        functMap.put("add","100000");
        functMap.put("sub","100010");
        functMap.put("and","100100");
        functMap.put("or","100101");
    }
    private InstrctionsPretreater instrctionsPretreater;
    private int instructionNum;

    public InstructionsDecoder(InstrctionsPretreater instrctionsPretreater,int instructionNum) {
        this.instrctionsPretreater = instrctionsPretreater;
        this.instructionNum = instructionNum;
    }

    /**
     *
     * @param instruction
     */
    public String decodeInstruction(String instruction){
        instruction = instruction.trim();
        String[] segments = instruction.split(" ");
        if(segments == null || segments.length != 2){
            throw new BadInstructionException("instruction must contains code and register");
        }
        String code = segments[0];
        String registersOrIm = segments[1];
        String binCode = "";
        if("addi".equals(code)){
            binCode += codeMap.get("addi");
            binCode += parseRegistersIm(registersOrIm);
        }
        if("add".equals(code)){
            binCode += codeMap.get("add");
            binCode += parseRegisters(registersOrIm);
            binCode += "00000";
            binCode += functMap.get("add");
        }
        if("sub".equals(code)){
            binCode += codeMap.get("sub");
            binCode += parseRegisters(registersOrIm);
            binCode += "00000";
            binCode += functMap.get("sub");
        }
        if("sw".equals(code)){
            binCode += codeMap.get("sw");
            binCode += parseLwSw(registersOrIm);
        }
        if("lw".equals(code)){
            binCode += codeMap.get("lw");
            binCode += parseLwSw(registersOrIm);
        }
        if("beq".equals(code)){
            binCode += codeMap.get("beq");
            binCode += parseBeq(registersOrIm);
        }
        if("jump".equals(code)){
            binCode += codeMap.get("jump");
            binCode += parseJump(registersOrIm);
        }
        if("movi".equals(code)){
            String trueCode = new PseudoInstructionsDecoder().decodeInstruction(instruction);
            binCode = decodeInstruction(trueCode);
        }
        if("mov".equals(code)){
            String trueCode = new PseudoInstructionsDecoder().decodeInstruction(instruction);
            binCode = decodeInstruction(trueCode);
        }
        return binCode;
    }


    private String parseRegistersIm(String registersOrIm){
        String[] segments = registersOrIm.split(",");
        String rt = segments[0];
        String rs = segments[1];
        String im = segments[2];
        String binCode = "";
        binCode += registersAddressMap.get(rs);
        binCode += registersAddressMap.get(rt);
        if(instrctionsPretreater.labelMap.containsKey(im)){
            im = String.valueOf(instrctionsPretreater.labelMap.get(im));

        }
        if(instrctionsPretreater.wordMap.containsKey(im)){
            im = String.valueOf(instrctionsPretreater.wordMap.get(im));
        }
        binCode += String.format("%016d",Integer.parseInt(Integer.toBinaryString(Integer.parseInt(im))));
        return binCode;
    }
    private String parseBeq(String registersOrIm){
        String[] segments = registersOrIm.split(",");
        String rt = segments[0];
        String rs = segments[1];
        String im = segments[2];
        String binCode = "";
        binCode += registersAddressMap.get(rs);
        binCode += registersAddressMap.get(rt);
        if(instrctionsPretreater.labelMap.containsKey(im)){
            im = String.valueOf(instrctionsPretreater.labelMap.get(im));

        }
        if(instrctionsPretreater.wordMap.containsKey(im)){
            im = String.valueOf(instrctionsPretreater.wordMap.get(im));
        }
        int targetPos = Short.parseShort(im);
        int branchPos = (targetPos - instructionNum);

        String str = String.format("%016d",Integer.parseInt(Integer.toBinaryString(branchPos)));
        binCode += str;
        return binCode;
    }
    private String parseLwSw(String registersOrIm){
        String[] segments = registersOrIm.split(",");
        String rt = segments[0];
        String imAndRs = segments[1];
        Matcher matcher = Pattern.compile("\\(.*?\\)").matcher(imAndRs);
        String binCode = "";

        if (matcher.find()) {
            String registerPart = matcher.group();
            String im = imAndRs.replace(registerPart,"");
            String rs = registerPart.replace("(","");
            rs = rs.replace(")","");
            binCode += registersAddressMap.get(rs);
            binCode += registersAddressMap.get(rt);
            binCode += String.format("%016d",Integer.parseInt(Integer.toBinaryString(Integer.parseInt(im))));
        }else {
            throw new BadInstructionException("register and im format error!");
        }
        return binCode;
    }

    private String parseRegisters(String registersOrIm){
        String[] segments = registersOrIm.split(",");
        String rd = segments[0];
        String rs = segments[1];
        String rt = segments[2];
        String binCode = "";
        binCode += registersAddressMap.get(rs);
        binCode += registersAddressMap.get(rt);
        binCode += registersAddressMap.get(rd);
        return binCode;
    }
    private String parseJump(String im){
        if(instrctionsPretreater.labelMap.containsKey(im)){
            im = String.valueOf(instrctionsPretreater.labelMap.get(im));

        }
        String binCode = String.format("%026d",Integer.parseInt(Integer.toBinaryString(Integer.parseInt(im))));
        return binCode;
    }

}
